package io.kiki.stack.http.feign;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;

public class ReflectiveFeign<C> extends Feign {

    private final ReflectiveFeignParseHandlersByName<C> targetToHandlersByName;
    private final InvocationHandlerFactory invocationHandlerFactory;
    private final AsyncContextSupplier<C> defaultContextSupplier;

    ReflectiveFeign(Contract contract, MethodHandlerFactory<C> methodHandlerFactory, InvocationHandlerFactory invocationHandlerFactory, AsyncContextSupplier<C> defaultContextSupplier) {
        this.targetToHandlersByName = new ReflectiveFeignParseHandlersByName<C>(contract, methodHandlerFactory);
        this.invocationHandlerFactory = invocationHandlerFactory;
        this.defaultContextSupplier = defaultContextSupplier;
    }

    /**
     * creates an api binding to the {@code target}. As this invokes reflection, care should be taken
     * to cache the result.
     */
    public <T> T newInstance(Target<T> target) {
        return newInstance(target, defaultContextSupplier.newContext());
    }

    @SuppressWarnings("unchecked")
    public <T> T newInstance(Target<T> target, C requestContext) {
        ReflectiveFeignTargetSpecificationVerifier.verify(target);

        Map<Method, MethodHandler> map = targetToHandlersByName.apply(target, requestContext);
        InvocationHandler invocationHandler = invocationHandlerFactory.create(target, map);
        T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, invocationHandler);

        for (MethodHandler methodHandler : map.values()) {
            if (methodHandler instanceof DefaultMethodHandler) {
                ((DefaultMethodHandler) methodHandler).bindTo(proxy);
            }
        }

        return proxy;
    }

}
